2
上一篇文章:MongoDB指南---3、MongoDB基础知识-数据类型
下一篇文章:MongoDB指南---5、创建、删除文档

本节将介绍如何将shell作为命令行工具的一部分来使用,如何对shell进行定制,以及shell的一些高级功能。
在上面的例子中,我们只是连接到了一个本地的mongod实例。事实上,可以将shell连接到任何MongoDB实例(只要你的计算机与MongoDB实例所在的计算机能够连通)。在启动shell时指定机器名和端口,就可以连接到一台不同的机器(或者端口):

$ mongo some-host:30000/myDB
MongoDB shell version: 2.4.0
connecting to: some-host:30000/myDB

db现在就指向了some-host:30000上的myDB数据库。
启动mongo shell时不连接到任何mongod有时很方便。通过--nodb参数启动shell,启动时就不会连接任何数据库:

$ mongo --nodb
MongoDB shell version: 2.4.0
>

启动之后,在需要时运行new Mongo(hostname)命令就可以连接到想要的mongod了:

> conn = new Mongo("some-host:30000")
connection to some-host:30000
> db = conn.getDB("myDB")
myDB

执行完这些命令之后,就可以像平常一样使用db了。任何时候都可以使用这些命令来连接到不同的数据库或者服务器。

2.7.1 shell小贴士

由于mongo是一个简化的JavaScript shell,可以通过查看JavaScript的在线文档得到大量帮助。对于MongoDB特有的功能,shell内置了帮助文档,可以使用help命令查看:

> help
    db.help()             help on db methods
    db.mycoll.help()      help on collection methods
    sh.help() sharding    helpers
    ...

    show dbs show database names
    show collections show collections in current database
    show users show users in current database
    ...

可以通过db.help()查看数据库级别的帮助,使用db.foo.help()查看集合级别的帮助。
如果想知道一个函数是做什么用的,可以直接在shell输入函数名(函数名后不要输入小括号),这样就可以看到相应函数的JavaScript实现代码。例如,如果想知道update函数的工作机制,或者是记不清参数的顺序,就可以像下面这样做:

> db.foo.update
function (query, obj, upsert, multi) {
    assert(query, "need a query");
    assert(obj, "need an object");
    this._validateObject(obj);
    this._mongo.update(this._fullName, query, obj,
                       upsert ? true : false, multi ? true : false);
} 

2.7.2 使用shell执行脚本

本书其他章都是以交互方式使用shell,但是也可以将希望执行的JavaScript文件传给shell。直接在命令行中传递脚本就可以了:

$ mongo script1.js script2.js script3.js
MongoDB shell version: 2.4.0
connecting to: test
I am script1.js
I am script2.js
I am script3.js
$

mongo shell会依次执行传入的脚本,然后退出。
如果希望使用指定的主机/端口上的mongod运行脚本,需要先指定地址,然后再跟上脚本文件的名称:

$ mongo --quiet server-1:30000/foo script1.js script2.js script3.js

这样可以将db指向server-1:30000上的foo数据库,然后执行这三个脚本。如上所示,运行shell时指定的命令行选项要出现在地址之前。
可以在脚本中使用print()函数将内容输出到标准输出(stdout),如上面的脚本所示。这样就可以在shell中使用管道命令。如果将shell脚本的输出管道给另一个使用--quiet选项的命令,就可以让shell不打印“MongoDB shell version…”提示。
也可以使用load()函数,从交互式shell中运行脚本:

> load("script1.js") 
I am script1.js
>

在脚本中可以访问db变量,以及其他全局变量。然而,shell辅助函数(比如"use db"和"show collections")不可以在文件中使用。这些辅助函数都有对应的JavaScript函数,如表2-1所示。
表2-1 shell辅助函数对应的JavaScript函数

辅助函数 等价函数
use foo db.getSisterDB("foo")
show dbs db.getMongo().getDBs()
show collections db.getCollectionNames()

可以使用脚本将变量注入到shell。例如,可以在脚本中简单地初始化一些常用的辅助函数。例如,下面的脚本对于本书的复制和分片部分内容非常有用。这个脚本定义了一个connectTo()函数,它连接到指定端口处的一个本地数据库,并且将db指向这个连接。

// defineConnectTo.js 

/**
 * 连接到指定的数据库,并且将db指向这个连接
 */
var connectTo = function(port, dbname) {
    if (!port) {
        port = 27017;
    }

    if (!dbname) {
        dbname = "test";
    }

    db = connect("localhost:"+port+"/"+dbname);
    return db;
};

如果在shell中加载这个脚本,connectTo函数就可以使用了。

> typeof connectTo
undefined
> load('defineConnectTo.js')
> typeof connectTo
function

除了添加辅助函数,还可以使用脚本将通用的任务和管理活动自动化。
默认情况下,shell会在运行shell时所处的目录中查找脚本(可以使用run("pwd")命令查看)。如果脚本不在当前目录中,可以为shell指定一个相对路径或者绝对路径。例如,如果脚本放置在~/my-scripts目录中,可以使用load("/home/myUser/my-scripts/defineConnectTo.js")命令来加载defineConnectTo.js。注意,load函数无法解析~符号。
也可以在shell中使用run()函数来执行命令行程序。可以在函数参数列表中指定程序所需的参数:

> run("ls", "-l", "/home/myUser/my-scripts/")
sh70352| -rw-r--r-- 1 myUser myUser 2012-12-13 13:15 defineConnectTo.js
sh70532| -rw-r--r-- 1 myUser myUser 2013-02-22 15:10 script1.js
sh70532| -rw-r--r-- 1 myUser myUser 2013-02-22 15:12 script2.js
sh70532| -rw-r--r-- 1 myUser myUser 2013-02-22 15:13 script3.js

通常来说,这种使用方式的局限性非常大,因为输出格式很奇怪,而且不支持管道。

2.7.3 创建.mongorc.js文件

如果某些脚本会被频繁加载,可以将它们添加到mongorc.js文件中。这个文件会在启动shell时自动运行。
例如,我们希望启动成功时让shell显示一句欢迎语。为此,我们在用户主目录下创建一个名为.mongorc.js的文件,向其中添加如下内容:

// mongorc.js

var compliment = ["attractive", "intelligent", "like Batman"];
var index = Math.floor(Math.random()*3);

print("Hello, you're looking particularly "+compliment[index]+" today!");

然后,当启动shell时,就会看到这样一些内容:

$ mongo
MongoDB shell version: 2.4.0-
preconnecting to: test
Hello, you're looking particularly like Batman today!
>

为了实用,可以使用这个脚本创建一些自己需要的全局变量,或者是为太长的名字创建一个简短的别名,也可以重写内置的函数。.mongorc.js最见的用途之一是移除那些比较“危险”的shell辅助函数。可以在这里集中重写这些方法,比如为dropDatabase或者deleteIndexes等辅助函数添加no选项,或者取消它们的定义。

var no = function() {
    print("Not on my watch.");
};

// 禁止删除数据库
db.dropDatabase = DB.prototype.dropDatabase = no;

// 禁止删除集合
DBCollection.prototype.drop = no;

// 禁止删除索引
DBCollection.prototype.dropIndex = no;

改变数据库函数时,要确保同时对db变量和DB原型进行改变(如上例所示)。如果只改变了其中一个,那么db变量可能没有改变,或者这些改变在新使用的所有数据库(运行use anotherDB命令)中都不会生效。
现在,如果试图调用这些函数,就会得到一条错误提示。注意,这种方式并不能保护数据库免受恶意用户的攻击,只能预防自己的手误。
如果在启动shell时指定--norc参数,就可以禁止加载.mongorc.js。

2.7.4 定制shell提示

将prompt变量设为一个字符串或者函数,就可以重写默认的shell提示。例如,如果正在运行一个需要耗时几分钟的查询,你可能希望完成时在shell提示中输出当前时间,这样就可以知道最后一个操作的完成时间了。

prompt = function() {
    return (new Date())+"> ";
};

另一个方便的提示是显示当前使用的数据库:

prompt = function() {
    if (typeof db == 'undefined') {
        return '(nodb)> ';
    }

    // 检查最后的数据库操作
    try {
        db.runCommand({getLastError:1});
    }
    catch (e) {
        print(e);
    }

    return db+"> ";
};

注意,提示函数应该返回字符串,而且应该小心谨慎地处理异常:如果提示中出现了异常会对用户造成困惑!
通常来说,提示函数中应该包含对getLastError的调用。这样可以捕获数据库错误,而且可以在shell断开时自动重新连接(比如重启了mongod)。
可以在.mongorc.js中定制自己想要的提示。也可以定制多个提示,在shell中可以自由切换。

2.7.5 编辑复合变量

shell的多行支持是非常有限的:不可以编辑之前的行。如果编辑到第15行时发现第1行有个错误,那会让人非常懊恼。因此,对于大块的代码或者是对象,你可能更愿意在编辑器中编辑。为了方便地调用编辑器,可以在shell中设置EDITOR变量(也可以在环境变量中设置):

> EDITOR="/usr/bin/emacs"

现在,如果想要编辑一个变量,可以使用"edit 变量名"这个命令,比如:

> var wap = db.books.findOne({title: "War and Peace"})
> edit wap

修改完成之后,保存并退出编辑器。变量就会被重新解析然后加载回shell。
在.mongorc.js文件中添加一行内容,EDITOR="编辑器路径";,以后就不必单独设置EDITOR变量了。

2.7.6 集合命名注意事项

可以使用db.collectionName获取一个集合的内容,但是,如果集合名称中包含保留字或者无效的JavaScript属性名称,db.collectionName就不能正常工作了。
假设要访问version集合,不能直接使用db.version,因为db.version是db的一个方法(会返回当前MongoDB服务器的版本):

> db.version
function () {
    return this.serverBuildInfo().version;
}

为了访问version集合,必须使用getCollection函数:

> db.getCollection("version");
test.version

如果集合名称中包含无效的JavaScript属性名称(比如foo-bar-baz和123abc),也可以使用这个函数来访问相应的集合。(注意,JavaScript属性名称只能包含字母、数字,以及"$"和"_"字符,而且不能以数字开头。)
还有一种方法可以访问以无效属性名称命名的集合,那就是使用数组访问语法:在JavaScript中,x.y等同于x['y']。也就是说,除了名称的字面量之外,还可以使用变量访问子集合。因此,如果需要对blog的每一个子集合进行操作,可以使用如下方式进行迭代:

var collections = ["posts", "comments", "authors"];
for (var i in collections) {
    print(db.blog[collections[i]]);
}

而不必这样:

print(db.blog.posts);
print(db.blog.comments);
print(db.blog.authors);

注意,不能使用db.blog.i,这样会被解释为test.blog.i,而不是test.blog.posts。必须使用db.blog[i]语法才能将i解释为相应的变量。
可以使用这种方式来访问那些名字怪异的集合:

> var name = "@#&!"
> db[name].find()

直接使用db.@#&!进行查询是非法的,但是可以使用db[name]。

上一篇文章:MongoDB指南---3、MongoDB基础知识-数据类型
下一篇文章:MongoDB指南---5、创建、删除文档

Mark
662 声望344 粉丝

talk is cheap,show me the code